package de.dhbw.ka.tit09.picsim.model;

import org.eclipse.core.databinding.observable.list.WritableList;

/**
 * Model of the PIC16C84 microcontroller components. This class is a singleton.
 * 
 * @author Andreas Bender
 * 
 */
public final class PICModelProvider {
	private static final PICModelProvider INSTANCE = new PICModelProvider();

	private PICStack stack = new PICStack();
	private PICTimers timers = new PICTimers();;

	private int wRegister;
	private SFRegisters sFRegisters = new SFRegisters();
	private GPRegisters gPRegisters = new GPRegisters();
	private EEPROMRegisters eEPROMRegisters = new EEPROMRegisters();

	private PICModelProvider() {
		resetPIC(true);
	}

	/**
	 * Returns the model instance.
	 * 
	 * @return the model instance
	 */
	public static PICModelProvider getInstance() {
		return INSTANCE;
	}

	/**
	 * Resets the PIC. Sets default values.
	 * 
	 * @param powerOnReset
	 */
	public void resetPIC(boolean powerOnReset) {
		if (powerOnReset) {
			setProgramCounter(0);
			setWRegister(0);

			initStack();
			initTimers();
			initSFRegisters();
			initGPRegisters();
			initEEPROM();
		} else {
			setProgramCounter(0);
		}
	}

	private void initStack() {
		stack.initStack();
	}

	private void initTimers() {
		timers.reset();
	}

	private void initSFRegisters() {
		sFRegisters.initSFRegisters();
	}

	private void initGPRegisters() {
		gPRegisters.initGPRegisters();
	}

	private void initEEPROM() {
		eEPROMRegisters.initEEPROM();
	}

	/**
	 * Returns the stack model object.
	 * 
	 * @return the PIC stack
	 */
	public PICStack getStack() {
		return stack;
	}

	/**
	 * Returns the special function registers model object.
	 * 
	 * @return the special function registers
	 */
	public SFRegisters getSFRegisters() {
		return sFRegisters;
	}

	/**
	 * Sets a register at a specified address.
	 * 
	 * @param address the register address
	 * @param value the new value
	 */
	public void setRegisterAtAddress(int address, int value) {
		// mask
		address = address & 0x7F;
		value = value & 0xFF;

		if (address > 0xB) {
			// gPRegisters.set(address - 0xC, value);
			gPRegisters.setGPRAtAddress(address, value);
		} else if (address == 0) {
			// indirect addressing
			int fsr = getSFRegisters().getFSR();
			if (fsr != 0) {
				boolean rp0 = getSFRegisters().getRP0();

				if ((fsr & 128) == 0) {
					getSFRegisters().setRP0(false);
				} else {
					getSFRegisters().setRP0(true);
				}

				setRegisterAtAddress(fsr, value);

				getSFRegisters().setRP0(rp0);
			}
		} else {
			sFRegisters.setSFRAtAddress(address, value);
		}
	}

	/**
	 * Returns the value of the register at the given address.
	 * 
	 * @param address the register address
	 * @return the register value
	 */
	public int getRegisterAtAddress(int address) {
		// mask
		address = address & 0x7F;

		if (address > 0xB) {
			// return (Integer) gPRegisters.get(address - 0xC);
			return gPRegisters.getGPRAtAddress(address);
		} else if (address == 0) {
			// indirect addressing
			int fsr = getSFRegisters().getFSR();

			if (fsr != 0) {
				boolean rp0 = getSFRegisters().getRP0();

				if ((fsr & 128) == 0) {
					getSFRegisters().setRP0(false);
				} else {
					getSFRegisters().setRP0(true);
				}

				int value = getRegisterAtAddress(getSFRegisters().getFSR());

				getSFRegisters().setRP0(rp0);

				return value;
			} else {
				return 0;
			}
		} else {
			return sFRegisters.getSFRAtAddress(address);
		}
	}

	/**
	 * Loads the program counter with the given value
	 * 
	 * @param programCounter the new program counter value
	 */
	public void setProgramCounter(int programCounter) {
		// mask (FIXME how to load with pclath?)
		getSFRegisters().setPCL(programCounter & 0xFF);// FIXME different pclath
														// etc. call&goto
		getSFRegisters().setPCLATH((programCounter & 0x1F00) >>> 8);
	}

	/**
	 * Increases the value of the program counter.
	 */
	public void incProgramCounter() {
		getSFRegisters().setPCL(getSFRegisters().getPCL() + 1);
	}

	/**
	 * Returns the value of the program counter.
	 * 
	 * @return the program counter value
	 */
	public int getProgramCounter() {
		return (getSFRegisters().getPCLATH() << 8) + getSFRegisters().getPCL();// FIXME
	}

	/**
	 * Sets the W register.
	 * 
	 * @param wRegister the new W register value
	 */
	public void setWRegister(int wRegister) {
		this.wRegister = wRegister & 0xFF;
		System.out
				.println("W: " + Integer.toHexString(wRegister).toUpperCase()); // FIXME
																				// for
																				// testing
																				// purposes
	}

	/**
	 * Returns the value of the W register.
	 * 
	 * @return the W register value
	 */
	public int getWRegister() {
		return wRegister;
	}

	/**
	 * Returns the general purpose register list.
	 * 
	 * @return the general purpose register list
	 */
	public WritableList getGPRegisters() {
		return gPRegisters;
	}

	/**
	 * Returns the EEPROM registers.
	 * 
	 * @return the EEPROM registers
	 */
	public EEPROMRegisters getEEPROMRegisters() {
		return eEPROMRegisters;
	}

	/**
	 * Returns the PIC timers.
	 * 
	 * @return the PIC timers object
	 */
	public PICTimers getTimers() {
		return timers;
	}
}
